﻿using System;
using System.ComponentModel;
using System.Globalization;
using System.Web;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Modules.Definitions;
using DotNetNuke.Entities.Portals;
using DotNetNuke.Entities.Tabs;
using DotNetNuke.UI.Skins;

namespace HeroldIT.Dnn.Modules
{
    /// <summary>
    /// A static class defining extension methods for module settings related work.
    /// </summary>
    public static class SettingsHelper
    {
        #region static fields

        private static readonly ModuleController moduleController = new ModuleController();

        #endregion

        #region static properties

        private static bool? areSkinObjectsInstalled;

        /// <summary>
        /// Gets whether HeroldIT SkinObjects is installed.
        /// </summary>
        public static bool AreSkinObjectsInstalled
        {
            get
            {
                if (null == areSkinObjectsInstalled)
                {
                    DesktopModuleController dmc = new DesktopModuleController();
                    areSkinObjectsInstalled = null != dmc.GetDesktopModuleByModuleName("HeroldIT.SkinObjects");
                }
                return areSkinObjectsInstalled.Value;
            }
        }

        #endregion

        #region static methods

        /// <summary>
        /// Gets the default value for the specified type's member. The value is based on an existing <see cref="DefaultValueAttribute"/> on the member or the default value of the requested type.
        /// </summary>
        /// <typeparam name="TValue">The type of value to return.</typeparam>
        /// <param name="type">The type, in which the requested member is declared.</param>
        /// <param name="memberName">The name of the <paramref name="type"/>'s member for which to get the default value.</param>
        /// <returns>The member's defined default value or the return type's default value.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static TValue GetDefaultValue<TValue>(this Type type, string memberName)
        {
            var memberInfo = type.GetMember(memberName);
            if (null != memberInfo && memberInfo.Length > 0)
            {
                var attributes = memberInfo[0].GetCustomAttributes(typeof(DefaultValueAttribute), false);
                if (null != attributes && attributes.Length > 0)
                {
                    var value = ((DefaultValueAttribute)attributes[0]).Value;
                    if (value is TValue)
                    {
                        return (TValue)value;
                    }
                }
            }
            return default(TValue);
        }

        /// <summary>
        /// Gets the module's setting.
        /// </summary>
        /// <typeparam name="TSetting">The type of the setting's value.</typeparam>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <param name="defaultValue">A default value for the setting.</param>
        /// <returns>The module's setting value if defined; otherwise <paramref name="defaultValue"/>.</returns>
        public static TSetting GetSetting<TSetting>(this IModuleSettingsProvider module, string key, TSetting defaultValue)
        {
            object result = module.Settings[key] as string;
            if (null == result)
                return defaultValue;

            if (result is TSetting)
                return (TSetting)result;

            if (typeof(TSetting).Equals(typeof(Guid)))
            {
                return (TSetting)(object)new Guid((string)result);
            }
            return (TSetting)Convert.ChangeType(result, typeof(TSetting), CultureInfo.InvariantCulture);
        }

        /// <summary>
        /// Gets the module's setting.
        /// </summary>
        /// <typeparam name="TSetting">The type of the setting's value.</typeparam>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <returns>The module's setting value if defined; otherwise the <typeparamref name="TSetting"/>'s default value.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static TSetting GetSetting<TSetting>(this IModuleSettingsProvider module, string key)
        {
            return module.GetSetting(key, default(TSetting));
        }

        /// <summary>
        /// Gets the module's setting.
        /// </summary>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <param name="defaultValue">A default value for the setting.</param>
        /// <returns>The module's setting value if defined; otherwise <paramref name="defaultValue"/>.</returns>
        public static string GetSetting(this IModuleSettingsProvider module, string key, string defaultValue)
        {
            return module.GetSetting<string>(key, defaultValue);
        }

        /// <summary>
        /// Gets the module's setting.
        /// </summary>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <returns>The module's setting value if defined; otherwise <c>null</c>.</returns>
        public static string GetSetting(this IModuleSettingsProvider module, string key)
        {
            return module.GetSetting<string>(key, null);
        }

        /// <summary>
        /// Get's the module's setting or default value.
        /// </summary>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="type">The type, in which the key is defined.</param>
        /// <param name="keyName">The name of the field defining the key of the module's setting.</param>
        /// <returns>The module's setting value if defined; otherwise the default value.</returns>
        public static string GetSettingOrDefault(this IModuleSettingsProvider module, Type type, string keyName)
        {
            try
            {
                return module.GetSetting((string)type.GetField(keyName).GetValue(null),
                                         type.GetDefaultValue<string>(keyName));
            }
            catch (NullReferenceException nre)
            {
                throw new ArgumentException(
                    String.Format(CultureInfo.InvariantCulture, "No member of name '{0}' found.", keyName), "keyName",
                    nre);
            }
        }

        /// <summary>
        /// Get's the module's setting or default value.
        /// </summary>
        /// <param name="module">The module for which to get the setting.</param>
        /// <param name="type">The type, in which the key is defined.</param>
        /// <param name="keyName">The name of the field defining the key of the module's setting.</param>
        /// <returns>The module's setting value if defined; otherwise the default value.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static TValue GetSettingOrDefault<TValue>(this IModuleSettingsProvider module, Type type, string keyName)
        {
            try
            {
                return module.GetSetting((string)type.GetField(keyName).GetValue(null),
                                                 type.GetDefaultValue<TValue>(keyName));
            }
            catch (NullReferenceException nre)
            {
                throw new ArgumentException(
                    String.Format(CultureInfo.InvariantCulture, "No member of name '{0}' found.", keyName), "keyName",
                    nre);
            }
        }

        /// <summary>
        /// Sets or deletes the module's tab-specific setting.
        /// </summary>
        /// <param name="module">The module for which to set the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <param name="value">The value to set the setting to or <c>null</c> to delete the setting.</param>
        public static void SetTabSetting(this IModuleSettingsProvider module, string key, string value)
        {
            if (null == value)
                moduleController.DeleteTabModuleSetting(module.ModuleConfiguration.TabModuleID, key);
            else
                moduleController.UpdateTabModuleSetting(module.ModuleConfiguration.TabModuleID, key, value);
        }

        /// <summary>
        /// Sets or deletes the module's setting.
        /// </summary>
        /// <param name="module">The module for which to set the setting.</param>
        /// <param name="key">The key of the module's setting.</param>
        /// <param name="value">The value to set the setting to or <c>null</c> to delete the setting.</param>
        public static void SetSetting(this IModuleSettingsProvider module, string key, string value)
        {
            if (null == value)
                moduleController.DeleteModuleSetting(module.ModuleConfiguration.ModuleID, key);
            else
                moduleController.UpdateModuleSetting(module.ModuleConfiguration.ModuleID, key, value);
        }

        private static TSetting AssertSettingInternal<TSetting>(this IModuleSettingsProvider module, string key)
        {
            if (!module.Settings.ContainsKey(key))
            {
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Setting `{0}` has not been set.", key));
            }
            return module.GetSetting<TSetting>(key);
        }

        /// <summary>
        /// Asserts whether the specified setting has been set for the module.
        /// </summary>
        /// <typeparam name="TSetting">The type of the setting's value.</typeparam>
        /// <param name="module">The module for which to assert the setting.</param>
        /// <param name="key">The setting's key.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter")]
        public static void AssertSetting<TSetting>(this IModuleSettingsProvider module, string key)
        {
            module.AssertSettingInternal<TSetting>(key);
        }

        /// <summary>
        /// Asserts whether the specified setting has been set for the module.
        /// </summary>
        /// <param name="module">The module for which to assert the setting.</param>
        /// <param name="key">The setting's key.</param>
        public static void AssertSetting(this IModuleSettingsProvider module, string key)
        {
            var result = module.AssertSettingInternal<string>(key);
            if (String.IsNullOrEmpty(result))
            {
                throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Setting `{0}` has not been set.", key));
            }
        }

        /// <summary>
        /// Creates a complete skin url by replacing the tokens [M], [P], [S], or [C] at the beginning of the given
        /// <paramref name="url"/> with the <paramref name="module"/>'s base path, portal's root path, skin path, or
        /// container path.
        /// </summary>
        /// <param name="module">The module using the skin.</param>
        /// <param name="url">The tokenized url to the skin.</param>
        /// <returns>The complete skin url.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
        public static string MakeSkinUrl(this PortalModuleBase module, string url)
        {
            if (url.StartsWith("[M]", StringComparison.Ordinal))
            {
                return url.Replace("[M]", module.ModulePath);
            }
            if (url.StartsWith("[P]", StringComparison.Ordinal))
            {
                return url.Replace("[P]", module.PortalSettings.HomeDirectory);
            }
            if (url.StartsWith("[S]", StringComparison.Ordinal))
            {
                TabInfo tab = new TabController().GetTab(module.ModuleConfiguration.TabID,
                                                         module.ModuleConfiguration.PortalID, false);
                if (String.IsNullOrEmpty(tab.SkinSrc))
                {
                    return SkinController.FormatSkinSrc(url.Replace("[S]",
                                                                    SkinController.FormatSkinPath(
                                                                        HeroldIT.Dnn.Modules.Compatibility.PortalSettings.Instance.DefaultPortalSkin)),
                                                        module.PortalSettings);
                }
                return SkinController.FormatSkinSrc(url.Replace("[S]",
                                                                SkinController.FormatSkinPath(
                                                                    tab.SkinSrc)),
                                                    module.PortalSettings);
            }
            if (url.StartsWith("[C]", StringComparison.Ordinal))
            {
                if (String.IsNullOrEmpty(module.ModuleConfiguration.ContainerSrc))
                {
                    return SkinController.FormatSkinSrc(url.Replace("[C]",
                                                                    SkinController.FormatSkinPath(
                                                                        HeroldIT.Dnn.Modules.Compatibility.PortalSettings.Instance.DefaultPortalContainer)),
                                                        module.PortalSettings);
                }
                return SkinController.FormatSkinSrc(url.Replace("[C]",
                                                                SkinController.FormatSkinPath(
                                                                    module.ModuleConfiguration.ContainerSrc)),
                                                    module.PortalSettings);
            }
            return url;
        }

        /// <summary>
        /// Creates a complete skin url by replacing the tokens [M], [P], [S], or [C] at the beginning of the given
        /// <paramref name="url"/> with the <paramref name="module"/>'s base path, portal's root path, skin path, or
        /// container path.
        /// </summary>
        /// <param name="module">The module using the skin.</param>
        /// <param name="url">The tokenized url to the skin.</param>
        /// <returns>The complete skin url.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1055:UriReturnValuesShouldNotBeStrings"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", MessageId = "1#")]
        public static string MakeSkinUrl(this ModuleInfo module, string url)
        {
            if (url.StartsWith("[M]", StringComparison.Ordinal))
            {
                if (null == module)
                    throw new ArgumentNullException("module");

                string modulePath = VirtualPathUtility.Combine("~/DesktopModules/", new DesktopModuleController().GetDesktopModule(new ModuleDefinitionController().GetModuleDefinition(module.ModuleDefID).DesktopModuleID).FolderName);
                if (!modulePath.EndsWith("/", StringComparison.Ordinal))
                    modulePath += "/";
                return url.Replace("[M]", modulePath);
            }
            PortalSettings portalSettings = PortalController.GetCurrentPortalSettings();
            if (url.StartsWith("[P]", StringComparison.Ordinal))
            {
                return url.Replace("[P]", portalSettings.HomeDirectory);
            }
            if (url.StartsWith("[S]", StringComparison.Ordinal))
            {
                if (null == module)
                {
                    return SkinController.FormatSkinSrc(url.Replace("[S]",
                                                                    SkinController.FormatSkinPath(
                                                                        HeroldIT.Dnn.Modules.Compatibility.PortalSettings.Instance.DefaultPortalSkin)),
                                                        portalSettings);
                }

                TabInfo tab = new TabController().GetTab(module.TabID,
                                                         module.PortalID, false);
                if (String.IsNullOrEmpty(tab.SkinSrc))
                {
                    return SkinController.FormatSkinSrc(url.Replace("[S]",
                                                                    SkinController.FormatSkinPath(
                                                                        HeroldIT.Dnn.Modules.Compatibility.PortalSettings.Instance.DefaultPortalSkin)),
                                                        portalSettings);
                }
                return SkinController.FormatSkinSrc(url.Replace("[S]",
                                                                SkinController.FormatSkinPath(
                                                                    tab.SkinSrc)),
                                                    portalSettings);
            }
            if (url.StartsWith("[C]", StringComparison.Ordinal))
            {
                if (null == module || String.IsNullOrEmpty(module.ContainerSrc))
                {
                    return SkinController.FormatSkinSrc(url.Replace("[C]",
                                                                    SkinController.FormatSkinPath(
                                                                        HeroldIT.Dnn.Modules.Compatibility.PortalSettings.Instance.DefaultPortalContainer)),
                                                        portalSettings);
                }
                return SkinController.FormatSkinSrc(url.Replace("[C]",
                                                                SkinController.FormatSkinPath(
                                                                    module.ContainerSrc)),
                                                    portalSettings);
            }
            return url;
        }

        #endregion
    }
}
